// Copyright (C) 2024 Kumo inc.
// Author: Jeff.li lijippy@163.com
// All rights reserved.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
//

#pragma once

#include <tuple>
#include <locale>
#include <sstream>
#include <kthread/internal/kthread.h>
#include <kthread/internal/mutex.h>
#include <type_traits>
#include <kthread/internal/condition_variable.h>

namespace kthread {


    class Kthread {
    public:
        Kthread() noexcept: _kid(INVALID_KTHREAD) {}

        ~Kthread();

        bool joinable() const noexcept { return _kid != INVALID_KTHREAD; }

        void join(bool crash_if_error = false);

        void detach(bool crash_if_error = false);

        kthread_t native_handle() noexcept { return _kid; }

        Kthread &operator=(Kthread &&rhs) noexcept {
            if (_kid != INVALID_KTHREAD) {
                std::terminate();
            }
            _kid = rhs._kid;
            rhs._kid = INVALID_KTHREAD;
            return *this;
        }

        Kthread(Kthread &&rhs) noexcept {
            if (_kid != INVALID_KTHREAD) {
                std::terminate();
            }
            _kid = rhs._kid;
            rhs._kid = INVALID_KTHREAD;
        }

        void swap(Kthread &rhs) noexcept { std::swap(_kid, rhs._kid); }

        /// stack size 1M
        template<class F, class...Args, std::enable_if_t<!std::is_same<turbo::remove_cvref_t<F>, Kthread>::value, int> = 0>
        turbo::Status run(F &&f, Args &&... args) noexcept;

        /// stack size 1M
        template<class F, class...Args, std::enable_if_t<!std::is_same<turbo::remove_cvref_t<F>, Kthread>::value, int> = 0>
        turbo::Status run_urgent(F &&f, Args &&... args) noexcept;

        /// stack size
        /// KTHREAD_ATTR_NORMAL 1M
        /// KTHREAD_ATTR_LARGE  8M
        /// KTHREAD_ATTR_SMALL  32k
        template<class F, class...Args, std::enable_if_t<!std::is_same<turbo::remove_cvref_t<F>, Kthread>::value, int> = 0>
        turbo::Status run(kthread_attr_t attr, F &&f, Args &&... args) noexcept;

        template<class F, class...Args, std::enable_if_t<!std::is_same<turbo::remove_cvref_t<F>, Kthread>::value, int> = 0>
        turbo::Status run_urgent(kthread_attr_t attr, F &&f, Args &&... args) noexcept;


    private:
        template<class F, class...Args, std::enable_if_t<!std::is_same<turbo::remove_cvref_t<F>, Kthread>::value, int> = 0>
        int run_impl(kthread_attr_t attr,bool urgent, F &&f, Args &&... args) noexcept;

        template<class Fp, class... Args>
        struct KthreadData {
            KthreadData(Fp &&f, Args &&...args_)
                    : func(std::forward<Fp>(f)),
                      args(std::forward<Args>(args_)...) {}

            std::decay_t<Fp> func;
            std::tuple<std::decay_t<Args>...> args;
        };

        template<class Fp, class... Args>
        static void *kthread_proxy(void *__vp) {
            std::unique_ptr<KthreadData<Fp, Args...>> p(static_cast<KthreadData<Fp, Args...> *>(__vp));
            std::apply(p->func, p->args);
            return nullptr;
        }

    private:
        kthread_t _kid;
        kthread_attr_t _attr;
    };

    template<class F, class...Args, std::enable_if_t<!std::is_same<turbo::remove_cvref_t<F>, Kthread>::value, int>>
    turbo::Status Kthread::run(F &&f, Args &&... args) noexcept {
        auto rs = run_impl(KTHREAD_ATTR_NORMAL, false, std::forward<F>(f), std::forward<Args>(args)...);
        if(rs != 0) {
            return turbo::make_status(rs, "fail to start kthread");
        }
        return turbo::OkStatus();
    }

    template<class F, class...Args, std::enable_if_t<!std::is_same<turbo::remove_cvref_t<F>, Kthread>::value, int>>
    turbo::Status Kthread::run_urgent(F &&f, Args &&...args) noexcept {
        auto rs = run_impl(KTHREAD_ATTR_NORMAL, true, std::forward<F>(f), std::forward<Args>(args)...);
        if(rs != 0) {
            turbo::make_status(rs, "fail to start kthread");
        }
        return turbo::OkStatus();
    }

    template<class F, class...Args, std::enable_if_t<!std::is_same<turbo::remove_cvref_t<F>, Kthread>::value, int>>
    turbo::Status Kthread::run(kthread_attr_t attr, F &&f, Args &&... args) noexcept {
        auto rs = run_impl(attr, false, std::forward<F>(f), std::forward<Args>(args)...);
        if(rs != 0) {
            turbo::make_status(rs, "fail to start kthread");
        }
        return turbo::OkStatus();
    }

    template<class F, class...Args, std::enable_if_t<!std::is_same<turbo::remove_cvref_t<F>, Kthread>::value, int>>
    turbo::Status Kthread::run_urgent(kthread_attr_t attr, F &&f, Args &&... args) noexcept {
        auto rs = run_impl(attr, true, std::forward<F>(f), std::forward<Args>(args)...);
        if(rs != 0) {
            turbo::make_status(rs, "fail to start kthread");
        }
        return turbo::OkStatus();
    }

    /// stack size
    template<class F, class...Args, std::enable_if_t<!std::is_same<turbo::remove_cvref_t<F>, Kthread>::value, int> = 0>
    turbo::Status run_small_stack(F &&f, Args &&... args) noexcept;
    /// stack size 8M
    template<class F, class...Args, std::enable_if_t<!std::is_same<turbo::remove_cvref_t<F>, Kthread>::value, int> = 0>
    turbo::Status run_large_stack(F &&f, Args &&... args) noexcept;

    template<class F, class...Args, std::enable_if_t<!std::is_same<turbo::remove_cvref_t<F>, Kthread>::value, int>>
    int Kthread::run_impl(kthread_attr_t attr,bool urgent, F &&f, Args &&... args) noexcept {
        _attr = attr;
        std::unique_ptr<KthreadData<F, Args...>> p(
                new KthreadData<F, Args...>(std::forward<F>(f), std::forward<Args>(args)...));
        int rc = -1;
        if(!urgent) {
            rc = kthread_start_background(&_kid, &_attr, kthread_proxy<F, Args...>, p.get());
        } else {
            rc = kthread_start_urgent(&_kid, &_attr, kthread_proxy<F, Args...>, p.get());
        }
        if(rc == 0 ){
            p.release();
        }
        return rc;
    }
}  // namespace kthread